﻿using Core.Info;
using Core.Log.SerilogExtend;
using Core.Log.SerilogExtend.Enricher;
using Microsoft.Extensions.Configuration;
using Serilog;
using Serilog.Core;
using Serilog.Events;
using Serilog.Sinks.File.Archive;
using System;
using System.IO;
using System.IO.Compression;
using System.Text;

namespace Core.Log
{
    /// <summary>
    /// 应用日志,使用前必须单例初始化一次
    /// <code>
    /// 日志上下文使用：
    /// var myLog = Serilog.Log.ForContext&lt;MyClass&gt;();    
    /// myLog.Information("Hello!");
    /// 或
    /// var appLog = AppLog.Logger.ForContext&lt;PrismAppDryIocBase&gt;();
    /// appLog.Information("Hello!");
    /// 
    /// var job = GetNextJob();
    /// var jobLog = Log.ForContext("JobId", job.Id);
    /// jobLog.Information("Running a new job");
    /// job.Run();
    /// jobLog.Information("Finished");
    /// 提示：当Log使用文本格式的接收器时，例如Serilog.Sinks.Console，您可以在输出模板的{Properties}中包含以打印所有未包含的上下文属性。
    /// </code>
    /// </summary>
    public static class AppLog
    {
        private static ILogger _logger;
        private static ILogger _jobScheduler;

        static AppLog()
        {
            
        }

        /// <summary>
        /// 日志等级控制
        /// </summary>
        private static LoggingLevelSwitch _levelSwitch = new LoggingLevelSwitch(LogEventLevel.Debug);

        /// <summary>
        /// 线程安全锁
        /// </summary>
        private static object _lock = new object();


        /// <summary>
        /// 动态更新日志级别控制
        /// </summary>
        /// <param name="logEventLevel">日志事件级别</param>
        public static void UpdateLoggingLevelSwitch(LogEventLevel logEventLevel)
        {
            if (_levelSwitch.MinimumLevel != logEventLevel)
            {
                lock (_lock)
                {
                    if (_levelSwitch.MinimumLevel != logEventLevel)
                    {
                        _levelSwitch.MinimumLevel = logEventLevel;
                    }
                }
            }
        }

        public static string _outputTemplate = "";

        /// <summary>
        /// 日志输出模板
        /// </summary>
        public static string OutputTemplate 
        { 
            get 
            {
                if (string.IsNullOrEmpty(_outputTemplate))
                {
                    lock(_lock)
                    {
                        if(string.IsNullOrEmpty(_outputTemplate))
                        {
                            //模板参考文档：https://github.com/serilog/serilog/wiki/Configuration-Basics
                            //模板格式化文档：https://github.com/serilog/serilog/wiki/Formatting-Output
                            _outputTemplate =
                                "{NewLine}[{Level:u4}] {Timestamp:yyyy-MM-dd HH:mm:ss.ffff} [线程Id： {ThreadId}]" +
                                "{NewLine}应用信息： {AppInfo:j}" +
                                "{NewLine}" +
                                new string('-', 15) + " 日志信息开始 " + new string('-', 15) +
                                "{NewLine}" +
                                "{Message:lj}" +
                                "{NewLine}" +
                                new string('-', 15) + " 日志信息结束 " + new string('-', 15) +
                                "{NewLine}日志属性： {Properties:j}" +
                                "{NewLine}{Exception}" +
                                "{NewLine}" +
                                new string('=', 50) +
                                "{NewLine}" +
                                "{NewLine}";//输出模板
                        }
                    }
                }
                return _outputTemplate; 
            }
        }

        private static LoggerConfiguration? _loggerConfiguration = null;
        private static LoggerConfiguration? _jobSchedulerLoggerConfiguration = null;

        /// <summary>
        /// 获取默认配置
        /// </summary>
        public static LoggerConfiguration LoggerConfiguration
        {
            get
            {
                if(_loggerConfiguration == null)
                {
                    lock(_lock)
                    {
                        if (_loggerConfiguration == null)
                        {
                            /* 日志级别说明
                                * 1. Verbose	详细- 跟踪信息和调试细节；一般只在异常情况下开启;  Verbose 是最详细的级别，很少（如果有的话）为生产应用程序启用。
                                * 2. Debug	调试- 内部控制流和诊断状态转储，以帮助查明已识别的问题;  调试用于内部系统事件，这些事件不一定可以从外部观察到，但在确定某事如何发生时很有用。
                                * 3. Information	信息- 感兴趣的事件或与外部观察者相关的事件；默认启用的最低日志记录级别;  信息事件描述了系统中发生的与其职责和功能相对应的事情。通常，这些是系统可以执行的可观察操作。
                                * 4. Warning	警告- 可能出现问题或服务/功能降级的指标;  当服务降级、受到威胁或行为可能超出其预期参数时，将使用警告级别事件。
                                * 5. Error	错误- 表明应用程序或连接的系统中出现故障;  当功能不可用或预期被破坏时，将使用 Error 事件。
                                * 6. Fatal	致命- 导致应用程序完全失败的严重错误;  最关键的级别，致命事件需要立即关注。
                                * 默认级别- 如果未MinimumLevel指定，则将处理Information级别事件和更高级别的事件。
                             */


                            /* 日志可以组件参考
                             * https://github.com/serilog/serilog/wiki/Provided-Sinks
                             */

                            //依赖包：Microsoft.Extensions.Configuration、Microsoft.Extensions.Configuration.FileExtensions、Microsoft.Extensions.Configuration.Json、Serilog.Settings.Configuration
                            //参见：https://github.com/serilog/serilog-settings-configuration/

                            //读取配置文件
                            var settingsJsonConfig = new ConfigurationBuilder()
                                .SetBasePath(AppInfo.ConfigDirectory)
                                .AddJsonFile("core.log.settings.json", false, true)
                                //.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", true)
                                .Build();
                            //var settingsConfig = new LoggerConfiguration().ReadFrom.Configuration(settingsJsonConfig);
                            var settingsConfigMinimumLevel = settingsJsonConfig["Serilog:MinimumLevel"];
                            _levelSwitch.MinimumLevel = SerilogConverter.LogLevelStringToLogEventLevel(settingsConfigMinimumLevel, _levelSwitch.MinimumLevel);

                            var logConfig = new LoggerConfiguration()
                            //配置Serilog.Events.LogEvents的扩展。Enricher可以添加、删除和修改与事件关联的属性。
                            //参考文档：https://github.com/serilog/serilog/wiki/Enrichment
                            .Enrich.With(new ThreadIdEnricher()) //扩展线程Id
                            .Enrich.With(new AppInfoEnricher()) //扩展应信息
                                                                //.MinimumLevel.Debug()
                                                                //.MinimumLevel.ControlledBy(_levelSwitch) //日志级别由_levelSwitch控制
                            .WriteTo.Console
                            (
                                outputTemplate: OutputTemplate,
                                applyThemeToRedirectedOutput: true
                            )
                            .WriteTo.Debug
                            (
                                outputTemplate: OutputTemplate
                            )
                            //滚动策略：按天滚动，最多保存31个文件，不限制文件大小
                            .WriteTo.File(
                                $"{Path.Combine(AppInfo.LogDirectory, ".log")}",//{AppDomain.CurrentDomain.BaseDirectory}/Logs/.log
                                outputTemplate: OutputTemplate,
                                //restrictedToMinimumLevel: LogEventLevel.Verbose,
                                rollingInterval: RollingInterval.Day,//日志按日保存，这样会在文件名称后自动加上日期后缀
                                rollOnFileSizeLimit: true,          // 限制单个文件的最大长度
                                retainedFileCountLimit: 31,         // 最大保存文件数,等于null时永远保留文件。
                                                                    //fileSizeLimitBytes: 10 * 1024 * 1024,      // 最大单个文件大小
                                fileSizeLimitBytes: null,      // 最大单个文件大小
                                encoding: Encoding.UTF8, // 文件字符编码
                                shared: true //多进程共享文件
                                             //hooks: new ArchiveHooks(CompressionLevel.Fastest)
                            )
                            //滚动策略：按天滚动，最多保存100个文件，不限制文件大小
                            .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Debug) //单独输出 Debug 级别
                                .WriteTo.File(
                                    $"{Path.Combine(AppInfo.LogDirectory, $"{nameof(LogEventLevel.Debug)}_.log")}",//{AppDomain.CurrentDomain.BaseDirectory}/Logs/.log
                                    outputTemplate: OutputTemplate,
                                    //restrictedToMinimumLevel: LogEventLevel.Verbose,
                                    rollingInterval: RollingInterval.Day,//日志按日保存，这样会在文件名称后自动加上日期后缀
                                    rollOnFileSizeLimit: true,          // 限制单个文件的最大长度
                                    retainedFileCountLimit: 31,         // 最大保存文件数,等于null时永远保留文件。
                                                                        //fileSizeLimitBytes: 10 * 1024 * 1024,      // 最大单个文件大小
                                    fileSizeLimitBytes: null,      // 最大单个文件大小
                                    encoding: Encoding.UTF8, // 文件字符编码
                                    shared: false, //多进程共享文件
                                    hooks: new ArchiveHooks(CompressionLevel.Fastest)
                            ))
                            //滚动策略：按天滚动，最多保存100个文件，不限制文件大小
                            .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Error) //单独输出 Error 级别
                                .WriteTo.File(
                                    $"{Path.Combine(AppInfo.LogDirectory, $"{nameof(LogEventLevel.Error)}_.log")}",//{AppDomain.CurrentDomain.BaseDirectory}/Logs/.log
                                    outputTemplate: OutputTemplate,
                                    //restrictedToMinimumLevel: LogEventLevel.Verbose,
                                    rollingInterval: RollingInterval.Day,//日志按日保存，这样会在文件名称后自动加上日期后缀
                                    rollOnFileSizeLimit: true,          // 限制单个文件的最大长度
                                    retainedFileCountLimit: 31,         // 最大保存文件数,等于null时永远保留文件。
                                                                        //fileSizeLimitBytes: 10 * 1024 * 1024,      // 最大单个文件大小
                                    fileSizeLimitBytes: null,      // 最大单个文件大小
                                    encoding: Encoding.UTF8, // 文件字符编码
                                    shared: false, //多进程共享文件
                                    hooks: new ArchiveHooks(CompressionLevel.Fastest)
                            ))
                            //滚动策略：按天滚动，最多保存100个文件，不限制文件大小
                            .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(e => e.Level == LogEventLevel.Fatal) //单独输出 Fatal 级别
                                .WriteTo.File(
                                    $"{Path.Combine(AppInfo.LogDirectory, $"{nameof(LogEventLevel.Fatal)}_.log")}",//{AppDomain.CurrentDomain.BaseDirectory}/Logs/.log
                                    outputTemplate: OutputTemplate,
                                    //restrictedToMinimumLevel: LogEventLevel.Verbose,
                                    rollingInterval: RollingInterval.Day,//日志按日保存，这样会在文件名称后自动加上日期后缀
                                    rollOnFileSizeLimit: true,          // 限制单个文件的最大长度
                                    retainedFileCountLimit: 31,         // 最大保存文件数,等于null时永远保留文件。
                                                                        //fileSizeLimitBytes: 10 * 1024 * 1024,      // 最大单个文件大小
                                    fileSizeLimitBytes: null,      // 最大单个文件大小
                                    encoding: Encoding.UTF8, // 文件字符编码
                                    shared: false, //多进程共享文件
                                    hooks: new ArchiveHooks(CompressionLevel.Fastest)
                            ))
                            //根据日志属性过滤
                            .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(p =>
                                {
                                    LogEventPropertyValue result = p.Properties.FirstOrDefault(c => c.Key == "LogSource").Value;
                                    if (result != null)
                                    {
                                        return result?.ToString() == "\"Custom\"";//指定的日志源
                                    }
                                    return false;
                                })
                                .WriteTo.File(
                                    $"{Path.Combine(AppInfo.LogDirectory, "Custom_.log")}",//{AppDomain.CurrentDomain.BaseDirectory}/Logs/.log
                                    outputTemplate: OutputTemplate,
                                    //restrictedToMinimumLevel: LogEventLevel.Verbose,
                                    rollingInterval: RollingInterval.Day,//日志按日保存，这样会在文件名称后自动加上日期后缀
                                    rollOnFileSizeLimit: true,          // 限制单个文件的最大长度
                                    retainedFileCountLimit: 31,         // 最大保存文件数,等于null时永远保留文件。
                                                                        //fileSizeLimitBytes: 10 * 1024 * 1024,      // 最大单个文件大小
                                    fileSizeLimitBytes: null,      // 最大单个文件大小
                                    encoding: Encoding.UTF8, // 文件字符编码
                                    shared: false, //多进程共享文件                                    
                                    hooks: new ArchiveHooks(CompressionLevel.Fastest)                                    
                            ));
                            logConfig.MinimumLevel.ControlledBy(_levelSwitch);
                            _loggerConfiguration = logConfig;
                        }
                    }
                }                
                return _loggerConfiguration;
            }
        }


        /// <summary>
        /// 获取默认配置
        /// </summary>
        public static LoggerConfiguration JobSchedulerLoggerConfiguration
        {
            get
            {
                if (_jobSchedulerLoggerConfiguration == null)
                {
                    lock (_lock)
                    {
                        if (_jobSchedulerLoggerConfiguration == null)
                        {
                            /* 日志级别说明
                                * 1. Verbose	详细- 跟踪信息和调试细节；一般只在异常情况下开启;  Verbose 是最详细的级别，很少（如果有的话）为生产应用程序启用。
                                * 2. Debug	调试- 内部控制流和诊断状态转储，以帮助查明已识别的问题;  调试用于内部系统事件，这些事件不一定可以从外部观察到，但在确定某事如何发生时很有用。
                                * 3. Information	信息- 感兴趣的事件或与外部观察者相关的事件；默认启用的最低日志记录级别;  信息事件描述了系统中发生的与其职责和功能相对应的事情。通常，这些是系统可以执行的可观察操作。
                                * 4. Warning	警告- 可能出现问题或服务/功能降级的指标;  当服务降级、受到威胁或行为可能超出其预期参数时，将使用警告级别事件。
                                * 5. Error	错误- 表明应用程序或连接的系统中出现故障;  当功能不可用或预期被破坏时，将使用 Error 事件。
                                * 6. Fatal	致命- 导致应用程序完全失败的严重错误;  最关键的级别，致命事件需要立即关注。
                                * 默认级别- 如果未MinimumLevel指定，则将处理Information级别事件和更高级别的事件。
                             */


                            /* 日志可以组件参考
                             * https://github.com/serilog/serilog/wiki/Provided-Sinks
                             */

                            //依赖包：Microsoft.Extensions.Configuration、Microsoft.Extensions.Configuration.FileExtensions、Microsoft.Extensions.Configuration.Json、Serilog.Settings.Configuration
                            //参见：https://github.com/serilog/serilog-settings-configuration/

                            //读取配置文件
                            var settingsJsonConfig = new ConfigurationBuilder()
                                .SetBasePath(AppInfo.ConfigDirectory)
                                .AddJsonFile("core.log.settings.json", false, true)
                                //.AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json", true)
                                .Build();
                            //var settingsConfig = new LoggerConfiguration().ReadFrom.Configuration(settingsJsonConfig);
                            var settingsConfigMinimumLevel = settingsJsonConfig["Serilog:MinimumLevel"];
                            _levelSwitch.MinimumLevel = SerilogConverter.LogLevelStringToLogEventLevel(settingsConfigMinimumLevel, _levelSwitch.MinimumLevel);

                            var logConfig = new LoggerConfiguration()
                            //配置Serilog.Events.LogEvents的扩展。Enricher可以添加、删除和修改与事件关联的属性。
                            //参考文档：https://github.com/serilog/serilog/wiki/Enrichment
                            .Enrich.With(new ThreadIdEnricher()) //扩展线程Id
                            .Enrich.With(new AppInfoEnricher()) //扩展应信息
                                                                //.MinimumLevel.Debug()
                                                                //.MinimumLevel.ControlledBy(_levelSwitch) //日志级别由_levelSwitch控制
                            .WriteTo.Console
                            (
                                outputTemplate: OutputTemplate,
                                applyThemeToRedirectedOutput: true
                            )
                            .WriteTo.Debug
                            (
                                outputTemplate: OutputTemplate
                            )
                            //滚动策略：按天滚动，最多保存31个文件，不限制文件大小
                            .WriteTo.File(
                                $"{Path.Combine(AppInfo.LogDirectory, "JobScheduler_.log")}",//{AppDomain.CurrentDomain.BaseDirectory}/Logs/.log
                                outputTemplate: OutputTemplate,
                                //restrictedToMinimumLevel: LogEventLevel.Verbose,
                                rollingInterval: RollingInterval.Day,//日志按日保存，这样会在文件名称后自动加上日期后缀
                                rollOnFileSizeLimit: true,          // 限制单个文件的最大长度
                                retainedFileCountLimit: 31,         // 最大保存文件数,等于null时永远保留文件。
                                                                    //fileSizeLimitBytes: 10 * 1024 * 1024,      // 最大单个文件大小
                                fileSizeLimitBytes: null,      // 最大单个文件大小
                                encoding: Encoding.UTF8, // 文件字符编码
                                shared: true //多进程共享文件
                                             //hooks: new ArchiveHooks(CompressionLevel.Fastest)
                            );
                            logConfig.MinimumLevel.ControlledBy(_levelSwitch);
                            _jobSchedulerLoggerConfiguration = logConfig;
                        }
                    }
                }
                return _jobSchedulerLoggerConfiguration;
            }
        }

        private static bool isInitialization = false;

        /// <summary>
        /// 初始化日志组件，有且仅能初始化一次
        /// </summary>
        public static void Initialization(LoggerConfiguration logConfig)
        {
            if (!isInitialization)
            {
                lock(_lock)
                {
                    if (!isInitialization)
                    {
                        //LoggerConfiguration logConfig = LoggerConfiguration;
                        _logger = logConfig.CreateLogger();
                        Serilog.Log.Logger = _logger;
                        isInitialization = true;
                    }
                }
            }     
        }

        /// <summary>
        /// 初始化作业调度日志组件，有且仅能初始化一次
        /// </summary>
        public static void InitializationJobScheduler(LoggerConfiguration jobSchedulerLogConfig)
        {
            if (_jobScheduler == null)
            {
                lock (_lock)
                {
                    if (_jobScheduler == null)
                    {
                        _jobScheduler = jobSchedulerLogConfig.CreateLogger();
                    }
                }
            }
        }


        /// <summary>
        /// Logger
        /// </summary>
        public static ILogger Logger => _logger;

        /// <summary>
        /// 作业调度日志
        /// </summary>
        public static ILogger JobScheduler => _jobScheduler;

        /// <summary>
        /// 关闭并刷新日志
        /// </summary>
        public static void CloseAndFlush()
        {
            Serilog.Log.CloseAndFlush();
        }
    }
}
